/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年5月29日
 */
package com.massyframework.beanskin.runtime.modular;

import java.io.Closeable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.ModuleExistsException;
import com.massyframework.beanskin.ModuleIdentifier;
import com.massyframework.beanskin.ModuleInstalledEvent;
import com.massyframework.beanskin.ModuleCreatedEvent;
import com.massyframework.beanskin.ModuleNotFoundException;
import com.massyframework.beanskin.ModuleRepository;
import com.massyframework.beanskin.ModuleDestoryingEvent;
import com.massyframework.beanskin.RequireLibraryDescriptor;
import com.massyframework.beanskin.SimpleModuleIdentifier;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.fragment.Fragment;
import com.massyframework.beanskin.fragment.FragmentBuilder;
import com.massyframework.beanskin.library.Library;
import com.massyframework.beanskin.license.License;
import com.massyframework.beanskin.license.LicenseBindedEvent;
import com.massyframework.beanskin.license.LicenseChangedEvent;
import com.massyframework.beanskin.license.LicenseToExpireEvent;
import com.massyframework.beanskin.license.LicenseUnbindedEvent;
import com.massyframework.beanskin.plugin.Plugin;
import com.massyframework.beanskin.plugin.PluginActivator;
import com.massyframework.beanskin.runtime.ModuleRegistration;
import com.massyframework.beanskin.runtime.ModuleRegistry;
import com.massyframework.beanskin.runtime.modular.fragment.DefaultFragmentBuilder;
import com.massyframework.beanskin.util.IOUtils;
import com.massyframework.beanskin.util.classloading.ClassLoaderUtils;

/**
 * 缺省的模块资源库
 * @author huangkh
 *
 */
public final class DefaultModuleRepository extends DefaultModuleEventPublisher 
	implements ModuleRepository, ModuleRegistry {

	private final Logger logger = LoggerFactory.getLogger(DefaultModuleRepository.class);
	
	private Map<ModuleIdentifier, RegistrationImpl<?>> moduleMap;
	private Map<Integer, RegistrationImpl<?>> idMap;
	
	/**
	 * 
	 */
	public DefaultModuleRepository() {
		this.moduleMap = new ConcurrentHashMap<>();
		this.idMap = new ConcurrentHashMap<>();
	}
		
	@Override
	public Module<?> findModuleById(int id) {
		return this.retrieveModule(this.idMap.get(id));
	}
	
	@Override
	public Module<?> findModuleByIdentifier(ModuleIdentifier identifier) {
		return  this.retrieveModule(this.moduleMap.get(identifier));
	}
	
	@Override
	public Module<?> findByFilter(Predicate<Module<?>> filter) {
		return this.retrieveModule(
				this.idMap.values().stream()
					.filter( registration -> {
						Module<?> module = this.retrieveModule(registration);
						return filter.test(module);
					})
					.findFirst()
					.orElse(null)
				);
	}
	
	@Override
	public Module<?> findModuleWithOptimal(RequireLibraryDescriptor require) {
		List<Module<?>> list =
				this.moduleMap.values().stream()
					.filter( registration -> require.isRequire(registration.module))
					.map (registration -> registration.module)
					.sorted()
					.collect(Collectors.toList());
		if (!list.isEmpty()) {
			return list.get(list.size()-1);
		}
		
		return null;
	}
	
	@Override
	public List<Module<?>> getModules() {
		return this.moduleMap.values().stream()
				.map( registration -> this.retrieveModule(registration))
				.collect(Collectors.toList());
	}
	
	@Override
	public List<Module<?>> getModules(Predicate<Module<?>> filter) {
		return this.idMap.values().stream()
				.map( registration -> this.retrieveModule(registration))
				.filter(filter)
				.collect(Collectors.toList());
	}
	
	@Override
	public <T extends Module<?>> List<T> getModules(Predicate<T> filter, Class<T> requireType) {
		return this.moduleMap.values().stream()
				.filter( registration ->{
					Module<?> module = this.retrieveModule(registration);
					return requireType.isAssignableFrom(module.getClass());
				})
				.map(registration -> requireType.cast(this.retrieveModule(registration)))
				.filter(filter)
				.collect(Collectors.toList());
	}
	@Override
	public int size() {
		return this.moduleMap.size();
	}	
	
	@Override
	public void reloadFragment(ModuleIdentifier identifier, Function<FragmentBuilder, Fragment> action)
			throws ModuleNotFoundException {
		Objects.requireNonNull(identifier, "\"identifier\" cannot be null.");
		Objects.requireNonNull(action, "\"action\" cannot be null.");
		
		RegistrationImpl<?> registration = this.moduleMap.get(identifier);
		if (registration == null) {
			throw new ModuleNotFoundException(identifier);
		}
		Module<?> module = registration.getModule();
		if (!(module instanceof Fragment)) {
			throw new ModuleNotFoundException(identifier);
		}
		
		Integer id = module.getId();
		registration.unregister();
		
		DefaultFragmentBuilder builder = new DefaultFragmentBuilder(id);
		Fragment fragment = action.apply(builder);
		if (fragment != null) {
			this.register(fragment, false);
		}
	}

	@Override
	public void register(Library library) throws ModuleExistsException {
		SimpleModuleIdentifier identifier =
				SimpleModuleIdentifier.clone(library).build();
		RegistrationImpl<Library> result = new RegistrationImpl<>(library); 
		if (this.moduleMap.putIfAbsent(identifier, result) != null) {
			throw new ModuleExistsException(identifier);
		}
		
		this.idMap.put(library.getId(), result);
		if (logger.isInfoEnabled()) {
			logger.info("install ".concat(this.getModuleType(library)).concat("[").concat(library.toIdentifier()).concat("] success."));
		}

		ModuleCreatedEvent event = new ModuleCreatedEvent(library);
		this.applyEventAsync(event);		
	}

	@Override
	public void register(Plugin plugin, Function<Plugin, PluginActivator> action) throws ModuleExistsException {
		SimpleModuleIdentifier identifier =
				SimpleModuleIdentifier.clone(plugin).build();
		PluginRegistration result = new PluginRegistration(plugin); 
		if (this.moduleMap.putIfAbsent(identifier, result) != null) {
			throw new ModuleExistsException(identifier);
		}
		
		this.idMap.put(plugin.getId(), result);
		result.setPluginActivator(action);
		if (logger.isInfoEnabled()) {
			logger.info("install ".concat(this.getModuleType(plugin)).concat("[").concat(plugin.toIdentifier()).concat("] success."));
		}
		
		ModuleCreatedEvent event = new ModuleCreatedEvent(plugin);
		this.applyEventAsync(event);	
	}

	@Override
	public void register(Assembly assembly) throws ModuleExistsException {
		SimpleModuleIdentifier identifier =
				SimpleModuleIdentifier.clone(assembly).build();
		RegistrationImpl<Assembly> result = new RegistrationImpl<>(assembly); 
		if (this.moduleMap.putIfAbsent(identifier, result) != null) {
			throw new ModuleExistsException(identifier);
		}
		
		this.idMap.put(assembly.getId(), result);
		if (logger.isInfoEnabled()) {
			logger.info("install ".concat(this.getModuleType(assembly)).concat("[").concat(assembly.toIdentifier()).concat("] success."));
		}

		ModuleCreatedEvent event = new ModuleCreatedEvent(assembly);
		this.applyEventAsync(event);
		
	}

	@Override
	public void register(Fragment fragment, boolean installing) throws ModuleExistsException {
		SimpleModuleIdentifier identifier =
				SimpleModuleIdentifier.clone(fragment).build();
		RegistrationImpl<Fragment>  result = new RegistrationImpl<>(fragment); 
		if (this.moduleMap.putIfAbsent(identifier, result) != null) {
			throw new ModuleExistsException(identifier);
		}
		
		this.idMap.put(fragment.getId(), result);
		if (logger.isInfoEnabled()) {
			logger.info("install ".concat(this.getModuleType(fragment)).concat("[").concat(fragment.toIdentifier()).concat("] success."));
		}
		
		if (installing) {
			ModuleInstalledEvent event = new ModuleInstalledEvent(fragment);
			this.applyEvent(event);
		}

		ModuleCreatedEvent event = new ModuleCreatedEvent(fragment);
		this.applyEventAsync(event);	
	}
	
	
	@Override
	public void unregisterPlugins() {
		List<PluginRegistration> registrations = this.moduleMap.values().stream()
				.filter( registration -> registration instanceof PluginRegistration)
				.map (registration -> (PluginRegistration)registration)
				.collect(Collectors.toList());
		
		Collections.sort(registrations,  (r1, r2) -> {
			return Integer.compare(r1.getModule().getId(), r2.getModule().getId());
		});
		
		Collections.reverse(registrations);
		for (PluginRegistration registration: registrations) {
			ClassLoader contextLoader = ClassLoaderUtils.setThreadContextClassLoader(registration.getModule().getModuleClassLoader());
			try {
				PluginActivator activator = registration.getPluginActivator();
				if (activator != null) {
					activator.destroy();
				}
			}finally {
				Thread.currentThread().setContextClassLoader(contextLoader);
			}
			
			registration.unregister();
		}
	}
	
	@Override
	public void bindLicense(License license) {
		List<Module<?>> modules = 
				this.getModules( module->{
					return module.getGroupId().equals(license.getGroupId()) &&
							module.getModuleId().equals(license.getModuleId()) &&
							license.getVersionRange().includes(module.getVersion());
				});
		for (Module<?> module: modules) {
			if (module instanceof AbstractModule) {
				boolean hasLicense = module.getLicense() != null;
				if (((AbstractModule<?>)module).comparisonAndReplace(license)) {
					if (hasLicense) {
						//发布替换事件
						LicenseChangedEvent event = new LicenseChangedEvent(module);
						this.applyEvent(event);
					}else {
						LicenseBindedEvent event = new LicenseBindedEvent(module);
						this.applyEvent(event);
					}
				}
			}
		}
	}
	
	@Override
	public void unbindLicense(License license) {
		List<Module<?>> modules = 
				this.getModules( module->{
					return module.getGroupId().equals(license.getGroupId()) &&
							module.getModuleId().equals(license.getModuleId()) &&
							license.getVersionRange().includes(module.getVersion());
				});
		for (Module<?> module: modules) {			
			if (module instanceof AbstractModule) {
				License current = module.getLicense();
				if (current != null) {
					if (current.equals(license)) {
						((AbstractModule<?>)module).comparisonAndReplace(null);
						LicenseUnbindedEvent event = new LicenseUnbindedEvent(module);
						this.applyEvent(event);
					}
				}
			}
		}		
	}
	
	/**
	 * 卸载模块
	 * @param module {@link Module},模块
	 * @return {@link boolean},是否卸载成功
	 */
	protected boolean uninstall(Module<?> module) {
		/*
		 * int id = module.getId(); RegistrationImpl<?> registration =
		 * this.idMap.get(id); if (registration != null) { return
		 * registration.unregister(); }
		 */
		
		return false;
	}
	
	@Override
	public boolean uninstall(Fragment fragment) {
		RegistrationImpl<?> registration = this.idMap.get(fragment.getId());
		if (registration != null) {
			if (registration.getModule() == fragment) {
				return registration.unregister();
			}
		}
		
		return false;
	}

	protected String getModuleType(Module<?> module) {
		if (module instanceof Library) {
			return "Library";
		}
		
		if (module instanceof Plugin) {
			return "Plugin";
		}
		
		if (module instanceof Assembly) {
			return "Assembly";
		}
		
		if (module instanceof Fragment) {
			return "Fragment";
		}
		
		return "Module";
	}
	
	/**
	 * 卸载模块
	 * @param registration
	 * @return
	 */
	protected synchronized boolean doUnregister(RegistrationImpl<?> registration) {
		if (this.idMap.get(registration.module.getId()) == registration) {
			ModuleDestoryingEvent event = new ModuleDestoryingEvent(registration.module);
			this.applyEvent(event);
			
			Module<?> unregistingModule = registration.getModule();
			
			List<Module<?>> modules =
					this.getModules( module->{
						License license = module.getLicense();
						return license == null ? false :
							license.isProvidedModule(unregistingModule);
					});
			if (!modules.isEmpty()) {
				for (Module<?> module: modules) {
					if (module instanceof AbstractModule) {
						((AbstractModule<?>)module).comparisonAndReplace(null);
						LicenseUnbindedEvent licenseEvent = new LicenseUnbindedEvent(module);
						this.applyEvent(licenseEvent);
					}
				}
			}
			
			this.idMap.remove(registration.module.getId());
			
			SimpleModuleIdentifier identifier =
					SimpleModuleIdentifier.clone(registration.module).build();
			this.moduleMap.remove(identifier);
			
			if (logger.isInfoEnabled()) {
				logger.info("uninstall ".concat(this.getModuleType(registration.module)).concat("[").concat(identifier.toIdentifier()).concat("] success."));
			}
			return true;
		}
		return false;
	}
	/**
	 * 取回模块
	 * @param registration
	 * @return
	 */
	protected Module<?> retrieveModule(RegistrationImpl<?> registration){
		if (registration== null) return null;
		return registration.module;
	}
	
	/**
	 * 许可将到期
	 * @param module
	 */
	protected void licenseTobeExpore(Module<?> module) {
		if (module != null) {
			LicenseToExpireEvent event = new LicenseToExpireEvent(module);
			this.applyEvent(event);
		}
	}
	
	/**
	 * 模块注册凭据
	 * @author huangkh
	 *
	 * @param <T>
	 */
	private class RegistrationImpl<T extends Module<?>> implements ModuleRegistration<T>{
		
		private T module;
		public RegistrationImpl(T module) {
			this.module = Objects.requireNonNull(module, "\"module\" cannot be null.");
		}

		@Override
		public boolean unregister() {
			boolean result =  doUnregister(this);
			if (this.module instanceof Closeable) {
				IOUtils.close((Closeable)this.module);
			}
			return result;
		}

		@Override
		public T getModule() {
			return this.module;
		}		
	}
	
	
	private class PluginRegistration extends RegistrationImpl<Plugin> {
		
		private PluginActivator activator;

		public PluginRegistration(Plugin module) {
			super(module);
		}
		
		/**
		 * 设置插件激活器
		 * @param action
		 */
		void setPluginActivator(Function<Plugin, PluginActivator> action) {
			this.activator = action.apply(this.getModule());
		}
		
		/**
		 * 插件激活器
		 * @return {@link PluginActivator}
		 */
		public PluginActivator getPluginActivator() {
			return this.activator;
		}
		
	}
}
